/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.myfaces.trinidad.bean;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;


/**
 * Base interface for FacesBean storage objects.
 * 
 */
public interface FacesBean
{
//  /**
//   * Returns the Type of this bean.
//   */
//  public Type getType();
//
//  /**
//   * Returns a property.  If the property has not been explicitly
//   * set, and the key supports bindings, and a ValueBinding has
//   * been set for this key, that ValueBinding will be evaluated.
//   * 
//   * @param key the property key
//   * @exception IllegalArgumentException if key is a list key
//   */
//  // TODO Additional version that takes a FacesContext?
//  public Object getProperty(PropertyKey key);
//
//  /**
//   *  Set a property.
//   * @exception IllegalArgumentException if key is a list key
//   */
//  public void setProperty(PropertyKey key, Object value);
//
//  /**
//   * Return a property, ignoring any value bindings.
//   * 
//   * @exception IllegalArgumentException if key is a list key
//   */
//  public Object getLocalProperty(PropertyKey key);
//  
//  /**
//   * Return the value binding for a key.
//   * @exception IllegalArgumentException if the property does
//   *   not support value bindings.
//   */
//  public ValueBinding getValueBinding(PropertyKey key);
//
//  /**
//   * Gets the current unevaluated value for the specified property key. 
//   * <p>The method will first look for a local value. If it exists, it will 
//   * be returned. If it does not and the bean supports value bindings, the 
//   * method will look for a binding with the specified key and return it 
//   * directly if it exists without evaluatig its value.</p>
//   * <p>This method is mainly used when:</p>
//   * <ul>
//   *   <li>The caller cannot ensure that FacesContext exists at the time 
//   *   of the call</li>
//   *   <li>The FacesContext does not yet contains the managed bean
//   *   referenced by the value binding</li>
//   *   <li>The managed bean referenced by the value binding is not yet 
//   *   in a coherent state to evaluate the expression</li>
//   * </ul>
//   * <p>The most common use case of this method is for message attributes 
//   * set on converters and validators using a value binding referencing 
//   * a managed bean created by <code>&lt;f:loadBundle/&gt;<code>. Since 
//   * loadBundle only creates its bean during the render response phase 
//   * while converter and validators take action during process validation 
//   * phase, the message property's value binding must be stored in a 
//   * special <code>FacesMessage</code> implementation that will evaluate 
//   * the binding only during render response.</p>
//   * 
//   * @param key the parameter key of the raw property value to get.
//   * 
//   * @return the local value of the specified key if it exists, a 
//   *         <code>ValueBinding</code> object if the specified key 
//   *         supports bindings and a binding was specified for that 
//   *         property, <code>null</code> otherwise.
//   * 
//   * @throws IllegalArgumentException if the specified key is a list key.
//   * 
//   * @see #getLocalProperty(PropertyKey)
//   * @see #getValueBinding(PropertyKey)
//   */
//  public Object getRawProperty(PropertyKey key);
//
//  /**
//   * Set the value binding for a key.
//   * @exception IllegalArgumentException if the property does
//   *   not support value bindings.
//   */
//  public void setValueBinding(PropertyKey key, ValueBinding binding);
//
//  /**
//   * Add an entry to a list.  The same value may be added
//   * repeatedly;  null is also a legal value.  (Consumers of
//   * this API can apply more stringent rules to specific keys
//   * in cover functions.)
//   * @exception IllegalArgumentException if the key is not a list key.
//   */
//  public void addEntry(PropertyKey listKey, Object value);
//
//  /**
//   * Remove an entry from a list.
//   * @exception IllegalArgumentException if the key is not a list key.
//   */
//  public void removeEntry(PropertyKey listKey, Object value);
//
//  /**
//   * Return as an array all elements of this key that 
//   * are instances of the specified class.
//   * @return an array whose instance type is the class
//   * @exception IllegalArgumentException if the key is not a list key.
//   */
//  // TODO This can, of course, be implemented on top of entries();
//  // consider moving to a utility function;  however, it's
//  // universally needed by all consumers, so...
//  public Object[] getEntries(PropertyKey listKey, Class<?> clazz);
//
//  /**
//   * Return true if at least one element of the list identified by
//   * this key is an instance of the specified class.
//   * @exception IllegalArgumentException if the key is not a list key.
//   */
//  public boolean containsEntry(PropertyKey listKey, Class<?> clazz);
//
//  /**
//   * Returns an iterator over all entries at this key.
//   * @exception IllegalArgumentException if the key is not a list key.
//   */
//  // TODO is this iterator read-only or read-write?
//  public Iterator<? extends Object> entries(PropertyKey listKey);
//
//  /**
//   * Copies all properties, bindings, and list entries from
//   * one bean to another.  If the beans are of different types,
//   * properties will be copied by name.  Incompatible properties will be
//   * ignored;  specifically, properties that are lists on only one
//   * of the beans or ValueBindings on the original bean that
//   * are not allowed on the target bean.
//   */
//  public void addAll(FacesBean from);
//
//  /**
//   * Returns a Set of all PropertyKeys that have either lists
//   *  or values attached.
//   */
//  public Set<PropertyKey> keySet();
//
//  /**
//   * Returns a Set of all PropertyKeys that have ValueBindings attached.
//   */
//  public Set<PropertyKey> bindingKeySet();
//
//  public void markInitialState();
//
//  /**
//   * Saves the state of a FacesBean.
//   */
//  public Object saveState(FacesContext context);
//
//  /**
//   * Restores the state of a FacesBean.
//   */
//  public void restoreState(FacesContext context, Object state);
//
  /**
   * Type of a FacesBean, encapsulating the set of registered
   * PropertyKeys.
   */
  // TODO Extract as interface?
  public static class Type
  {
    public Type()
    {
      this(null);
    }

    public Type(Type superType)
    {
      _superType = superType;
      _init();
    }

    /**
     * Find an existing key by name.
     */
    public PropertyKey findKey(String name)
    {
      return _keyMap.get(name);
    }

    /**
     * Find an existing key by index.
     */
    public PropertyKey findKey(int index)
    {
      if ((index < 0) || (index >= _keyList.size()))
        return null;
      
      return _keyList.get(index);
    }

    /**
     * Register a new key.
     * @exception IllegalStateException if the type is already locked,
     *    or the key does not already exists.
     */
    public final PropertyKey registerKey(
      String   name,
      Class<?> type,
      Object   defaultValue)
    {
      return registerKey(name, type, defaultValue, 0);
    }

    /**
     * Register a new key.
     * @exception IllegalStateException if the type is already locked,
     *    or the key does not already exists.
     */
    public final PropertyKey registerKey(
      String   name,
      Class<?> type)
    {
      return registerKey(name, type, null, 0);
    }

    /**
     * Register a new key.
     * @exception IllegalStateException if the type is already locked,
     *    or the key does not already exists.
     */
    public final PropertyKey registerKey(
      String name)
    {
      return registerKey(name, Object.class, null, 0);
    }

    /**
     * Register a new key.
     * @exception IllegalStateException if the type is already locked,
     *    or the key does not already exists.
     */
    public final PropertyKey registerKey(
      String name,
      int    capabilities)
    {
      return registerKey(name, Object.class, null, capabilities);
    }
//
//    /**
//     * Register a new key.
//     * @exception IllegalStateException if the type is already locked,
//     *    or the key does not already exists.
//     */
//    public final PropertyKey registerKey(
//      String   name,
//      Class<?> type,
//      int      capabilities)
//    {
//      return registerKey(name, type, null, capabilities);
//    }
//
//    /**
//     * Add an alias to an existing PropertyKey.
//     * @exception IllegalStateException if the type is already locked,
//     *    or a key already exists at the alias.
//     */
//    public PropertyKey registerAlias(PropertyKey key, String alias)
//    {
//      _checkLocked();
//      
//      if (findKey(alias) != null)
//        throw new IllegalStateException();
//
//      _keyMap.put(alias, key);
//      return key;
//    }
//    
//
    /**
     * Register a new key with a set of capabilities.
     * @exception IllegalStateException if the type is already locked,
     *    or the key already exists.
     */
    public PropertyKey registerKey(
      String   name, 
      Class<?> type,
      Object   defaultValue,
      int      capabilities)
    {
      _checkLocked();
      _checkName(name);

      PropertyKey key = createPropertyKey(name,
                                          type,
                                          defaultValue,
                                          capabilities,
                                          getNextIndex());
      addKey(key);
      return key;
    }
//    
//    
//    /**
//     * Locks the type object, preventing further changes.
//     */
//    public void lock()
//    {
//      _isLocked = true;
//    }
//
//    /**
//     * Locks the type object, preventing further changes.
//     */
//    public void lockAndRegister(
//       /*String renderKitId,*/
//       String componentFamily,
//       String rendererType)
//    {
//      lock();
//      // =-=AEW We don't yet have the renderKitId available here yet
//      TypeRepository.registerType(/*renderKitId, */
//                                  componentFamily,
//                                  rendererType,
//                                  this);
//    }
//
//    /**
//     * Returns the iterator of registered property keys, excluding aliases.
//     */
//    public Iterator<PropertyKey> keys()
//    {
//      return _keyList.iterator();
//    }
//
    protected PropertyKey createPropertyKey(
      String   name,
      Class<?> type,
      Object   defaultValue,
      int      capabilities,
      int      index)
    {
      if (_superType != null)
      {
        return _superType.createPropertyKey(name, type, defaultValue,
                                            capabilities, index);
      }

      return new PropertyKey(name, type, defaultValue, capabilities, index);
    }
//
    /**
     * Return the next available index.
     */
    protected int getNextIndex()
    {
      int index = _index;
      _index = index + 1;
      return index;
    }


    /**
     * Add a key to the type.
     * @exception IllegalStateException if the type is already locked,
     *    or a key with that name or index already exists.
     */
    protected void addKey(PropertyKey key)
    {
      _checkLocked();
      
      // Restore the old key
      PropertyKey oldValue = _keyMap.put(key.getName(), key);
      if (oldValue != null)
      {
        _keyMap.put(key.getName(), oldValue);
//        throw new IllegalStateException(_LOG.getMessage(
//          "NAME_ALREADY_REGISTERED", key.getName()));
      }
      
      int index = key.getIndex();
      if (index >= 0)
      {
        _expandListToIndex(_keyList, index);
        oldValue = _keyList.set(index, key);
        if (oldValue != null)
          {
            _keyList.set(index, oldValue);
//            throw new IllegalStateException(_LOG.getMessage(
//              "INDEX_ALREADY_REGISTERED", index));
          }
      }
      
      // Set the backpointer
      key.__setOwner(this);
    }
     
    
    static private void _expandListToIndex(ArrayList<?> list, int count)
    {
      list.ensureCapacity(count + 1);
      int addCount = (count + 1) - list.size();
      for (int i = 0; i < addCount; i++)
        list.add(null);
    }

    /**
     * @todo initial size of map, and type of map
     * @todo initial size of list, and type of list
     * @todo build combined data structure
     */
    private void _init()
    {
      _keyMap = new HashMap<String, PropertyKey>();
      _keyList = new ArrayList<PropertyKey>();

      if (_superType != null)
      {
        _keyMap.putAll(_superType._keyMap);
        _keyList.addAll(_superType._keyList);
        _index = _superType._index;
//        _superType.lock();
      }
    }

    private void _checkLocked()
    {
//      if (_isLocked)
//        throw new IllegalStateException(_LOG.getMessage(
//          "TYPE_ALREADY_LOCKED"));
    }

    private void _checkName(String name)
    {
      if (findKey(name) != null)
      {
//        throw new IllegalStateException(_LOG.getMessage(
//          "NAME_ALREADY_REGISTERED", name));
      }
    }
//
    private Map<String, PropertyKey> _keyMap;
    private ArrayList<PropertyKey>   _keyList;
    private boolean   _isLocked;
    private int       _index;
    private Type      _superType;
//    static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(Type.class); 
  }
}
